package redis

import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8"
	"github.com/shopspring/decimal"
	"sync"
)

// Leaderboard 定义排行榜结构体
type Leaderboard struct {
	client *redis.Client
}

// UserScore 存储用户的ID和得分，用于返回的排行榜信息
type UserScore struct {
	UserID string
	Score  decimal.Decimal
}

var (
	once        sync.Once
	leaderBoard *Leaderboard
)

func GetLeaderBoardInstance() *Leaderboard {
	once.Do(func() {
		leaderBoard = NewLeaderboard()
	})

	return leaderBoard
}

// NewLeaderboard 初始化 Redis 客户端和排行榜键
func NewLeaderboard() *Leaderboard {
	return &Leaderboard{
		client: KV,
	}
}

// AddOrUpdateUserScore 添加或更新用户的得分
func (lb *Leaderboard) AddOrUpdateUserScore(ctx context.Context, keyPrefix string, userID string, rawScore int, count int) error {
	// 计算综合得分: 分数/1000 + 统计次数
	score := float64(rawScore) + float64(count)/1000000.0

	// 使用 ZAdd 将用户得分存入 Redis 排行榜
	_, err := lb.client.ZAdd(ctx, keyPrefix, &redis.Z{
		Member: userID,
		Score:  score,
	}).Result()
	if err != nil {
		return fmt.Errorf("failed to add or update user score: %w", err)
	}
	return nil
}

// AddOrUpdateUserScoreByIncrement 添加或更新用户的得分
func (lb *Leaderboard) AddOrUpdateUserScoreByIncrement(ctx context.Context, keyPrefix string, userID string, rawScore int, count int) error {
	// 计算综合得分: 分数/1000 + 统计次数
	baseValue := decimal.NewFromFloat(1000000.0)
	score := decimal.NewFromInt(int64(rawScore)).Add(decimal.NewFromInt(int64(count)).Div(baseValue)).Round(6)

	// 使用 ZAdd 将用户得分存入 Redis 排行榜
	v, _ := score.Float64()
	_, err := lb.client.ZIncrBy(ctx, keyPrefix, v, userID).Result()
	if err != nil {
		return fmt.Errorf("failed to add or update user score: %w", err)
	}
	return nil
}

// GetUserRank 获取用户的排名（按分数从高到低）
func (lb *Leaderboard) GetUserRank(ctx context.Context, keyPrefix, userID string) (int64, error) {
	// ZRevRank 返回成员的排名，分数从高到低
	rank, err := lb.client.ZRevRank(ctx, keyPrefix, userID).Result()
	if err == redis.Nil {
		return -1, fmt.Errorf("user not found in leaderboard")
	} else if err != nil {
		return -1, fmt.Errorf("failed to get user rank: %w", err)
	}
	// Redis 排名从 0 开始，+1 以符合一般理解的排名
	return rank + 1, nil
}

// GetUserScore 获取用户的排名（按分数从高到低）
func (lb *Leaderboard) GetUserScore(ctx context.Context, keyPrefix, userID string) (float32, error) {
	// ZRevRank 返回成员的排名，分数从高到低
	scores, err := lb.client.ZScore(ctx, keyPrefix, userID).Result()
	if err == redis.Nil {
		return -1, fmt.Errorf("user not found in leaderboard")
	} else if err != nil {
		return -1, fmt.Errorf("failed to get user rank: %w", err)
	}
	// Redis 排名从 0 开始，+1 以符合一般理解的排名
	return float32(scores), nil
}

// GetTopUsers 获取排行榜前 N 名用户
func (lb *Leaderboard) GetTopUsers(ctx context.Context, key string, limit int) ([]UserScore, error) {
	// ZRevRangeWithScores 返回指定区间的成员和分数
	results, err := lb.client.ZRevRangeWithScores(ctx, key, 0, int64(limit-1)).Result()
	if err != nil {
		return nil, fmt.Errorf("failed to get top users: %w", err)
	}

	// 将结果转换为 UserScore 列表
	userScores := make([]UserScore, 0, len(results))
	for _, result := range results {
		userScores = append(userScores, UserScore{
			UserID: result.Member.(string),
			Score:  decimal.NewFromFloat(result.Score),
		})
	}
	return userScores, nil
}
